﻿using Microsoft.Extensions.Caching.Redis;
using Newtonsoft.Json;
using StackExchange.Redis;

namespace Infrastructure.Cache
{
    /// <summary>
    /// RedisCacheManager 类实现了 ICacheManager 接口，用于封装 Redis 缓存的基本操作。
    /// 它包括对缓存的增加、删除、查询、替换等操作，以及处理缓存的过期时间设置。
    /// </summary>
    public class RedisCacheManager : ICacheManager
    {
        // Redis 数据库实例
        protected IDatabase _cache;

        // Redis 连接器实例
        private ConnectionMultiplexer _connection;

        // Redis 实例名称
        private readonly string _instance;

        /// <summary>
        /// RedisCacheManager 的构造函数，初始化 Redis 连接并指定数据库。
        /// </summary>
        /// <param name="options">Redis 配置选项，包括服务器地址等信息</param>
        /// <param name="database">数据库编号，默认值为 0</param>
        public RedisCacheManager(RedisCacheOptions options, int database = 0)
        {
            // 初始化 Redis 连接器，连接配置来自 options.Configuration
            _connection = ConnectionMultiplexer.Connect(options.Configuration);

            // 获取指定数据库
            _cache = _connection.GetDatabase(database);

            // 设置实例名称
            _instance = options.InstanceName;
        }

        /// <summary>
        /// 获取 Redis 中键的前缀，通常用于标识 Redis 实例。
        /// </summary>
        /// <param name="key">缓存键</param>
        /// <returns>带实例名称的键</returns>
        public string GetKeyForRedis(string key) => _instance + key;

        // ----------------------------- Exists -----------------------------
        /// <summary>
        /// 检查指定的键是否存在于 Redis 中。
        /// </summary>
        /// <param name="key">缓存键</param>
        /// <returns>如果键存在，返回 true，否则返回 false</returns>
        public bool Exists(string key)
        {
            if (key == null)
                throw new ArgumentNullException(nameof(key));

            // 检查 Redis 中是否存在指定的键
            return _cache.KeyExists(GetKeyForRedis(key), CommandFlags.None);
        }

        // ----------------------------- Add 方法重载 -----------------------------
        /// <summary>
        /// 向 Redis 中添加一个键值对。
        /// </summary>
        /// <param name="key">缓存键</param>
        /// <param name="value">缓存值</param>
        /// <returns>如果添加成功，返回 true，否则返回 false</returns>
        public bool Add(string key, object value)
        {
            if (key == null)
                throw new ArgumentNullException(nameof(key));

            // 使用 JSON 序列化对象并添加到 Redis 中
            return _cache.StringSet(
                key: GetKeyForRedis(key),
                value: JsonConvert.SerializeObject(value, new JsonSerializerSettings
                {
                    NullValueHandling = NullValueHandling.Ignore // 忽略 null 值
                }),
                expiry: null, // 不设置过期时间
                when: When.Always,
                flags: CommandFlags.None
            );
        }

        /// <summary>
        /// 向 Redis 中添加一个键值对，并设置过期时间。
        /// </summary>
        /// <param name="key">缓存键</param>
        /// <param name="value">缓存值</param>
        /// <param name="expiresSliding">滑动过期时间</param>
        /// <param name="expiressAbsoulte">绝对过期时间</param>
        /// <returns>如果添加成功，返回 true，否则返回 false</returns>
        public bool Add(string key, object value, TimeSpan expiresSliding, TimeSpan expiressAbsoulte)
        {
            if (key == null)
                throw new ArgumentNullException(nameof(key));

            // 序列化对象并设置绝对过期时间
            return _cache.StringSet(
                key: GetKeyForRedis(key),
                value: JsonConvert.SerializeObject(value, new JsonSerializerSettings
                {
                    NullValueHandling = NullValueHandling.Ignore
                }),
                expiry: expiressAbsoulte, // 设置绝对过期时间
                when: When.Always,
                flags: CommandFlags.None
            );
        }

        /// <summary>
        /// 向 Redis 中添加一个键值对，并设置过期时间，可以选择滑动过期或绝对过期。
        /// </summary>
        /// <param name="key">缓存键</param>
        /// <param name="value">缓存值</param>
        /// <param name="expiresIn">过期时间</param>
        /// <param name="isSliding">是否使用滑动过期</param>
        /// <returns>如果添加成功，返回 true，否则返回 false</returns>
        public bool Add(string key, object value, TimeSpan expiresIn, bool isSliding = false)
        {
            if (key == null)
                throw new ArgumentNullException(nameof(key));

            // 序列化对象并设置过期时间
            return _cache.StringSet(
                key: GetKeyForRedis(key),
                value: JsonConvert.SerializeObject(value),
                expiry: expiresIn, // 设置过期时间
                when: When.Always,
                flags: CommandFlags.None
            );
        }

        // ----------------------------- Remove -----------------------------
        /// <summary>
        /// 删除指定的缓存键。
        /// </summary>
        /// <param name="key">缓存键</param>
        /// <returns>如果删除成功，返回 true，否则返回 false</returns>
        public bool Remove(string key)
        {
            if (key == null)
                throw new ArgumentNullException(nameof(key));

            // 删除 Redis 中的键
            return _cache.KeyDelete(GetKeyForRedis(key), CommandFlags.None);
        }

        /// <summary>
        /// 删除一组缓存键。
        /// </summary>
        /// <param name="keys">缓存键集合</param>
        public void RemoveAll(IEnumerable<string> keys)
        {
            if (keys == null)
                throw new ArgumentNullException(nameof(keys));

            // 遍历删除所有键
            keys.ToList().ForEach(key => Remove(key));
        }

        // ----------------------------- Get 方法 -----------------------------
        /// <summary>
        /// 获取 Redis 中指定键的值，并将其反序列化为指定类型。
        /// </summary>
        /// <param name="key">缓存键</param>
        /// <typeparam name="T">返回的对象类型</typeparam>
        /// <returns>如果键存在，返回反序列化后的对象，否则返回 null</returns>
        public T Get<T>(string key) where T : class
        {
            if (key == null)
                throw new ArgumentNullException(nameof(key));

            // 获取值并反序列化为对象
            RedisValue value = _cache.StringGet(GetKeyForRedis(key), CommandFlags.None);
            return value.HasValue
                ? JsonConvert.DeserializeObject<T>(value.ToString())
                : null;
        }

        /// <summary>
        /// 获取 Redis 中指定键的值，并反序列化为对象。
        /// </summary>
        /// <param name="key">缓存键</param>
        /// <returns>如果键存在，返回反序列化后的对象，否则返回 null</returns>
        public object Get(string key)
        {
            if (key == null)
                throw new ArgumentNullException(nameof(key));

            // 获取值并反序列化为对象
            RedisValue value = _cache.StringGet(GetKeyForRedis(key), CommandFlags.None);
            return value.HasValue
                ? JsonConvert.DeserializeObject<object>(value.ToString())
                : null;
        }

        /// <summary>
        /// 获取一组缓存键的值，并将其反序列化为对象集合。
        /// </summary>
        /// <param name="keys">缓存键集合</param>
        /// <returns>键和值的字典</returns>
        public IDictionary<string, object> GetAll(IEnumerable<string> keys)
        {
            if (keys == null)
            {
                throw new ArgumentNullException("keys");
            }
            Dictionary<string, object> dict = new Dictionary<string, object>();
            keys.ToList().ForEach(delegate (string item)
            {
                dict.Add(item, Get(GetKeyForRedis(item)));
            });
            return dict;
        }

        // ----------------------------- Replace -----------------------------
        /// <summary>
        /// 替换指定的缓存键的值，如果键不存在则添加。
        /// </summary>
        /// <param name="key">缓存键</param>
        /// <param name="value">缓存值</param>
        /// <returns>如果替换成功，返回 true，否则返回 false</returns>
        public bool Replace(string key, object value)
        {
            if (key == null)
            {
                throw new ArgumentNullException("key");
            }
            if (Exists(key) && !Remove(key))
            {
                return false;
            }
            return Add(key, value);
        }

        /// <summary>
        /// 替换指定的缓存键的值，并设置过期时间。
        /// </summary>
        /// <param name="key">缓存键</param>
        /// <param name="value">缓存值</param>
        /// <param name="expiresSliding">滑动过期时间</param>
        /// <param name="expiressAbsoulte">绝对过期时间</param>
        /// <returns>如果替换成功，返回 true，否则返回 false</returns>
        public bool Replace(string key, object value, TimeSpan expiresSliding, TimeSpan expiressAbsoulte)
        {
            if (key == null)
            {
                throw new ArgumentNullException("key");
            }
            if (Exists(key) && !Remove(key))
            {
                return false;
            }
            return Add(key, value, expiresSliding, expiressAbsoulte);
        }

        /// <summary>
        /// 替换指定的缓存键的值，并设置过期时间。
        /// </summary>
        /// <param name="key">缓存键</param>
        /// <param name="value">缓存值</param>
        /// <param name="expiresIn">过期时间</param>
        /// <param name="isSliding">是否使用滑动过期</param>
        /// <returns>如果替换成功，返回 true，否则返回 false</returns>
        public bool Replace(string key, object value, TimeSpan expiresIn, bool isSliding = false)
        {
            if (key == null)
            {
                throw new ArgumentNullException("key");
            }
            if (Exists(key) && !Remove(key))
            {
                return false;
            }
            return Add(key, value, expiresIn, isSliding);
        }

        /// <summary>
        /// 释放资源，关闭 Redis 连接。
        /// </summary>
        public void Dispose()
        {
            if (_connection != null)
            {
                _connection.Dispose();
            }
            GC.SuppressFinalize(this);
        }
    }

}
